# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import datetime
import logging
import json
from typing import List, Optional

from tornado import gen

from coin.base.param_util import to_list
from coin.base.timestamp import get_timestamp
import coin.flow.topic_map as topic_map
from coin.exchange.bittrex_v1.kr_rest.product import BittrexProduct


class _BittrexPrivateQueryContext(object):
  def __init__(self, http_client, ioloop, publisher, logger, products, period, working_orders):
    self._http_client = http_client
    self._ioloop = ioloop
    self._publisher = publisher
    self._logger = logger
    self._products = products
    self._period = period
    self._working_orders = working_orders

    self._stopped = False
    self._querying = False

  def stop(self):
    self._http_client.stop()
    self._stopped = True

  def start(self):
    if self._stopped:
      return
    self._ioloop.add_timeout(self._period, self._on_timeout)

  def _on_response(self, query, timestamp, response):
    if response is None or response.error:
      return

    data = json.loads(response.body)
    message = {'query': query, 'data': data}
    self._publisher.write(timestamp, message)

  @gen.coroutine
  def _query_balance(self):
    self._logger.debug('Bittrex query balance.')
    try:
      response = yield self._http_client.get('account/getbalances')
      self._on_response(
          'balance',
          get_timestamp(),  # TODO(jaewon): Approve me
          response)
    except Exception:
      self._logger.exception('Exception on query balance.')

  @gen.coroutine
  def _query_filled_orders(self, product):
    try:
      response = yield self._http_client.get('account/getorderhistory',
                                             {'market': product.native_symbol})
      self._on_response(
          'orders',
          get_timestamp(),  # TODO(jaewon): Approve me
          response)
    except Exception:
      self._logger.exception('Exception on procession a response: %s' % product.symbol)

  @gen.coroutine
  def _query_order_status(self, order_id):
    self._logger.debug('Bittrex query order status: %s.' % order_id)
    try:
      response = yield self._http_client.get('account/getorder', {'uuid': order_id})
      self._on_response(
          'order',
          get_timestamp(),  # TODO(jaewon): Approve me
          response)
    except Exception:
      self._logger.exception('Exception on procession a response of query order status: %s'
                             % order_id)

  @gen.coroutine
  def _query_open_orders(self):
    self._logger.debug('Bittrex query open orders.')
    try:
      response = yield self._http_client.get('market/getopenorders')
      self._on_response(
          'open_orders',
          get_timestamp(),  # TODO(jaewon): Approve me
          response)
    except Exception:
      self._logger.exception('Exception on procession a response of query open orders')

  @gen.coroutine
  def _run_periodic_query(self):
    self._querying = True
    self._logger.debug('Start querying %d symbols' % len(self._products))
    try:
      for internal_order_id, order in self._working_orders.items():
        if order.accepted:
          self._query_order_status(order.internal['server_order_id'])
      self._query_open_orders()
      self._query_balance()
    finally:
      self._querying = False
      if not self._stopped:
        self._logger.debug('Query done')
      else:
        self._logger.debug('Stopped')

  def _on_timeout(self):
    if self._stopped:
      return
    if not self._querying:
      self._ioloop.add_callback(self._run_periodic_query)
    else:
      self._logger.info('Still qeurying. Skip querying this time')
    self._ioloop.add_timeout(self._period, self._on_timeout)


class BittrexPrivateQueryer(object):
  def __init__(self,
               queue,
               worker_id: str,
               products: List[BittrexProduct],
               working_orders,
               period=None,
               logger=None):
    self._logger = logger or logging.getLogger(__name__)
    self._products = products
    self._period = period or datetime.timedelta(seconds=0.3)
    self._working_orders = working_orders

    self._context = None
    self._publisher = None
    self._init_publisher(queue, worker_id)

  def __del__(self):
    self.stop()
    self._publisher.close()

  def _init_publisher(self, queue, worker_id):
    topic_id = topic_map.search_topic_id('bittrex_rest_private_%s' % worker_id)[0][1]
    self._publisher = queue.get_writer(topic_id=topic_id)

  def start(self, http_client, ioloop, later_td: Optional[datetime.timedelta] = None):
    assert self._context is None
    self._context = _BittrexPrivateQueryContext(http_client,
                                                ioloop,
                                                self._publisher,
                                                self._logger,
                                                self._products,
                                                self._period,
                                                self._working_orders)

    if later_td is None:
      self._context.start()
    else:
      ioloop.add_timeout(later_td, self._context.start)

  def stop(self):
    if self._context is not None:
      self._context.stop()
      self._context = None
